/*
 * Copyright (C) 2000-2008 Jens Nixdorf and various contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of the Software, its documentation and marketing & publicity
 * materials, and acknowledgment shall be given in the documentation, materials
 * and software packages that this Software was used.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package org.mopore.jat.board;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.event.MouseEvent;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.mopore.jat.util.ImageService;

/**
 * ButtonContainer
 * 
 * @author Daniel Drachau, PRODYNA GmbH
 * @version $Id: $
 */
public class ButtonBarPainter {

    private Logger logger;

    private Point start;

    private int space = 6, smallIcon = 32, bigIcon = 70, xDragOffset = 0, yDragOffset = 0;

    private int xUndoAction = 0, yUndoAction = (bigIcon - smallIcon) / 2, xRedoAction = xUndoAction
            + smallIcon
            + space, yRedoAction = yUndoAction, xBigger = xRedoAction + smallIcon + space,
            yBigger = 0, xSmaller = xBigger, ySmaller = bigIcon - smallIcon, xBlue = xBigger
                    + space
                    + smallIcon, yBlue = 0, xRed = xBlue + bigIcon / 2, yRed = 0, xYellow = xRed
                    + bigIcon
                    / 2, yYellow = 0, labelXSize,labelYSize;

    private ButtonBarArea buttonArea;

    private Dimension resolution;

    private ActionButton undoMarkingButton, redoMarkingButton, biggerStrokeButton,
            smallerStrokeButton;

    private ColorButton blueStrokeButton, redStrokeButton, yellowStrokeButton;

    private BufferedImage label;

    private MouseEvent possibleMouseDrag;

    private boolean wasMoved;

    private Vector<Button> buttons;

    private Color currentColor = Color.YELLOW;

    private Integer currentSize = 20, minSize = 5, maxSize = 60;

    private MarkingQueue markingQueue;

    public ButtonBarPainter(BufferedImage image, MarkingQueue markingQueue) {
        this.logger = Logger.getLogger(ButtonBarPainter.class);
        this.markingQueue = markingQueue;
        this.undoMarkingButton = new ActionButton("small_undo.gif");
        this.redoMarkingButton = new ActionButton("small_redo.gif");
        this.biggerStrokeButton = new ActionButton("small_plus.gif");
        this.smallerStrokeButton = new ActionButton("small_minus.gif");

        this.blueStrokeButton = new ColorButton("big_blue.gif");
        this.redStrokeButton = new ColorButton("big_red.gif");
        this.yellowStrokeButton = new ColorButton("big_yellow.gif");

        this.label = ImageService.load("/images/board/logo.gif");
        this.labelXSize = this.label.getWidth();
        this.labelYSize = this.label.getHeight();

        this.buttons = new Vector<Button>();
        this.buttons.add(this.undoMarkingButton);
        this.buttons.add(this.redoMarkingButton);
        this.buttons.add(this.smallerStrokeButton);
        this.buttons.add(this.biggerStrokeButton);
        this.buttons.add(this.blueStrokeButton);
        this.buttons.add(this.redStrokeButton);
        this.buttons.add(this.yellowStrokeButton);

        this.possibleMouseDrag = null;
        this.wasMoved = false;
        this.currentColor = Color.YELLOW;
        this.currentSize = 20;
        this.start = new Point(image.getWidth() - this.getWidth() - this.getHeight()/2, this
                .getHeight()/2);
        this.resolution = new Dimension(image.getWidth(), image.getHeight());
        this.yellowStrokeButton.setActive(true);
    }

    private int getWidth() {
        return 2 * bigIcon + 3 * smallIcon + 3 * space;
    }

    private int getHeight() {
        return bigIcon + labelYSize;
    }

    public void paint(final Graphics2D g2, final Composite origComposite, BufferedImage screenImage) {

        this.validateCommandButtons();
        // draw buttons
        this.drawButton(g2, undoMarkingButton, start.x + xUndoAction, start.y + yUndoAction);
        this.drawButton(g2, redoMarkingButton, start.x + xRedoAction, start.y + yRedoAction);
        this.drawButton(g2, biggerStrokeButton, start.x + xBigger, start.y + yBigger);
        this.drawButton(g2, smallerStrokeButton, start.x + xSmaller, start.y + ySmaller);
        this.drawButton(g2, blueStrokeButton, start.x + xBlue, start.y + yBlue);
        this.drawButton(g2, redStrokeButton, start.x + xRed, start.y + yRed);
        this.drawButton(g2, yellowStrokeButton, start.x + xYellow, start.y + yYellow);
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
        g2.drawImage(label, start.x + getWidth() - labelXSize, start.y + getHeight() - labelYSize,
                null);
        // end drawing buttons

        g2.setComposite(origComposite);
    }

    private void drawButton(final Graphics2D g2, Button button, int x, int y) {
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, button.getAlpha()));
        g2.drawImage(button.getActivePicture(), x, y, null);
    }


    /**
     * @return Returns the current color for the new markers
     */
    public Color getCurrentMarkingColor() {
        return this.currentColor;
    }

    /**
     * @return Returns the current size the new markers
     */
    public Integer getCurrentSize() {
        return this.currentSize;
    }

    /**
     * 
     * @param e
     *            the event which should be analysed
     * @return Returns true if the event was handled
     */
    public boolean handleMouseClickEvent(MouseEvent e) {
        boolean handle = this.getButtonBarArea().contains(e.getPoint());
        if (handle) {
            this.possibleMouseDrag = e;
            xDragOffset = e.getPoint().x - this.start.x;
            yDragOffset = e.getPoint().y - this.start.y;
        }

        return handle;
    }

    public void validateCommandButtons() {
        this.undoMarkingButton.setEnable(this.markingQueue.canUndo());
        this.redoMarkingButton.setEnable(this.markingQueue.canRedo());
        this.biggerStrokeButton.setEnable(this.canBigger());
        this.smallerStrokeButton.setEnable(this.canSmaller());
    }

    private boolean canSmaller() {
        return this.currentSize > this.minSize;
    }

    private boolean canBigger() {
        return this.currentSize < this.maxSize;
    }

    /**
     * Registers a mouse move and moves the buttons if it has to
     * 
     * @param e
     *            the actual mouse position
     */
    public boolean handleMouseDraggedEvent(MouseEvent e) {
        if (this.possibleMouseDrag != null) {
            if (Math.abs(this.possibleMouseDrag.getPoint().x - e.getPoint().x) < 10
                    && Math.abs(this.possibleMouseDrag.getPoint().y - e.getPoint().y) < 10)
                return false;
            this.moveTo(e.getPoint());
            this.wasMoved = true;
            return true;
        }
        return false;
    }

    /**
     * @param e
     *            the mouse drag event
     * @return Returns true if the event was handled by the ButtonBarPainter
     */
    public boolean handleMouseReleasedEvent(MouseEvent e) {
        boolean result = this.possibleMouseDrag != null;
        if (result) {
            boolean justClicked = Math.abs(this.possibleMouseDrag.getPoint().x - e.getPoint().x) < 3
                    && Math.abs(this.possibleMouseDrag.getPoint().y - e.getPoint().y) < 3;
            if (justClicked || !this.wasMoved)
                try {
                    if (this.buttonArea.getUndoArea().contains(e.getPoint()))
                        this.markingQueue.undo();
                    if (this.buttonArea.getRedoArea().contains(e.getPoint()))
                        this.markingQueue.redo();
                    if (this.buttonArea.getBiggerStrokeArea().contains(e.getPoint()))
                        this.biggerStroke();
                    if (this.buttonArea.getSmallerStrokeArea().contains(e.getPoint()))
                        this.smallerStroke();
                    if (this.buttonArea.getBlueMarkerArea().contains(e.getPoint())) {
                        this.blueStrokeButton.setActive(true);
                        this.redStrokeButton.setActive(false);
                        this.yellowStrokeButton.setActive(false);
                        this.currentColor = Color.BLUE;
                    }
                    if (this.buttonArea.getRedMarkerArea().contains(e.getPoint())) {
                        this.blueStrokeButton.setActive(false);
                        this.redStrokeButton.setActive(true);
                        this.yellowStrokeButton.setActive(false);
                        this.currentColor = Color.RED;
                    }
                    if (this.buttonArea.getYellowMarkerArea().contains(e.getPoint())) {
                        this.blueStrokeButton.setActive(false);
                        this.redStrokeButton.setActive(false);
                        this.yellowStrokeButton.setActive(true);
                        this.currentColor = Color.YELLOW;
                    }
                } catch (Exception e1) {
                    this.logger.debug("could not play a klick sound in the button area", e1);
                }
        }
        this.possibleMouseDrag = null;
        this.wasMoved = false;
        return result;
    }

    public void smallerStroke() {
        if (this.currentSize > this.minSize)
            this.currentSize = this.currentSize - 5;
    }

    public void biggerStroke() {
        if (this.currentSize < this.maxSize)
            this.currentSize = this.currentSize + 5;
    }

    /**
     * @param e
     */
    public void handleMouseMovedEvent(MouseEvent e) {
        for (Button b : this.buttons)
            b.setHover(false);
        if (this.getButtonBarArea().contains(e.getPoint())) {

            this.undoMarkingButton.setHover(this.buttonArea.getUndoArea().contains(e.getPoint()));
            this.redoMarkingButton.setHover(this.buttonArea.getRedoArea().contains(e.getPoint()));
            this.smallerStrokeButton.setHover(this.buttonArea.getSmallerStrokeArea().contains(
                    e.getPoint()));
            this.biggerStrokeButton.setHover(this.buttonArea.getBiggerStrokeArea().contains(
                    e.getPoint()));
            this.blueStrokeButton.setHover(this.buttonArea.getBlueMarkerArea().contains(
                    e.getPoint()));
            this.redStrokeButton
                    .setHover(this.buttonArea.getRedMarkerArea().contains(e.getPoint()));
            this.yellowStrokeButton.setHover(this.buttonArea.getYellowMarkerArea().contains(
                    e.getPoint()));
            return;
        }
    }

    /**
     * @return Returns the buttonBar.
     */
    public Area getButtonBarArea() {
        if (this.buttonArea == null) {
            int x = this.start.x, y = this.start.y;
            Area undoArea = this.getSmallArea(x + xUndoAction, y + yUndoAction);
            Area redoArea = this.getSmallArea(x + xRedoAction, y + yRedoAction);
            Area biggerStroke = this.getSmallArea(x + xBigger, y + yBigger);
            Area smallerStroke = this.getSmallArea(x + xSmaller, y + ySmaller);
            Area blueMarker = this.getBigArea(x + xBlue, y + yBlue);
            Area redMarker = this.getBigArea(x + xRed, y + yRed);
            Area yellowMarker = this.getBigArea(x + xYellow, y + yYellow);

            this.buttonArea = new ButtonBarArea(new CommandBarArea(undoArea, redoArea,
                    smallerStroke, biggerStroke), blueMarker, redMarker, yellowMarker);
        }
        return this.buttonArea;
    }

    private Area getBigArea(int x, int y) {
        Polygon polygon = new Polygon();
        polygon.addPoint(x + 1, y + 17);
        polygon.addPoint(x + 11, y + 5);
        polygon.addPoint(x + 25, y + 0);
        polygon.addPoint(x + 40, y + 0);
        polygon.addPoint(x + 66, y + 48);
        polygon.addPoint(x + 69, y + 58);
        polygon.addPoint(x + 68, y + 65);
        polygon.addPoint(x + 64, y + 69);
        polygon.addPoint(x + 48, y + 69);
        polygon.addPoint(x + 37, y + 64);
        polygon.addPoint(x + 30, y + 60);
        polygon.addPoint(x + 18, y + 51);
        polygon.addPoint(x + 5, y + 38);
        polygon.addPoint(x + 0, y + 28);
        polygon.addPoint(x + 0, y + 20);
        return new Area(polygon);
    }

    private Area getSmallArea(int x, int y) {
        Polygon polygon = new Polygon();
        polygon.addPoint(x + 0, y + 13);
        polygon.addPoint(x + 7, y + 5);
        polygon.addPoint(x + 17, y + 0);
        polygon.addPoint(x + 27, y + 0);
        polygon.addPoint(x + 31, y + 4);
        polygon.addPoint(x + 31, y + 14);
        polygon.addPoint(x + 29, y + 25);
        polygon.addPoint(x + 24, y + 31);
        polygon.addPoint(x + 14, y + 31);
        polygon.addPoint(x + 7, y + 28);
        polygon.addPoint(x + 0, y + 21);
        return new Area(polygon);
    }

    public void moveTo(Point p) {
        if (p.x > resolution.width - this.getWidth() + xDragOffset)
            p = new Point(resolution.width - this.getWidth(), p.y);
        else if (p.x - xDragOffset < 0)
            p = new Point(0, p.y);
        else
            p = new Point(p.x - xDragOffset, p.y);

        if (p.y > resolution.height - this.getHeight() + yDragOffset)
            p = new Point(p.x, resolution.height - this.getHeight());
        else if (p.y - yDragOffset < 0)
            p = new Point(p.x, 0);
        else
            p = new Point(p.x, p.y - yDragOffset);

        this.buttonArea = null;
        this.start = p;
    }
}

class CommandBarArea extends Area {

    final Area undoArea, redoArea, smallerStrokeArea, biggerStrokeArea;

    final Area commandGroup, sizeGroup;

    public CommandBarArea(Area undoArea, Area redoArea, Area smallerStrokeArea,
            Area biggerStrokeArea) {
        this.undoArea = undoArea;
        this.redoArea = redoArea;
        this.smallerStrokeArea = smallerStrokeArea;
        this.biggerStrokeArea = biggerStrokeArea;

        this.commandGroup = new Area();
        this.commandGroup.add(undoArea);
        this.commandGroup.add(redoArea);

        this.sizeGroup = new Area();
        this.sizeGroup.add(smallerStrokeArea);
        this.sizeGroup.add(biggerStrokeArea);
    }

    /**
     * @return the biggerStrokeArea
     */
    public Area getBiggerStrokeArea() {
        return biggerStrokeArea;
    }

    /**
     * @return the redoArea
     */
    public Area getRedoArea() {
        return redoArea;
    }

    /**
     * @return the smallerStrokeArea
     */
    public Area getSmallerStrokeArea() {
        return smallerStrokeArea;
    }

    /**
     * @return the undoArea
     */
    public Area getUndoArea() {
        return undoArea;
    }

    /**
     * @return the commandGroup
     */
    public Area getCommandGroup() {
        return commandGroup;
    }

    /**
     * @return the sizeGroup
     */
    public Area getSizeGroup() {
        return sizeGroup;
    }
}

class ButtonBarArea extends Area {

    final Area blueMarkerArea, redMarkerArea, yellowMarkerArea, colorGroup;

    final CommandBarArea commandGroup;

    public ButtonBarArea(CommandBarArea commandBarArea, Area blueMarkerArea, Area redMarkerArea,
            Area yellowMarkerArea) {
        this.blueMarkerArea = blueMarkerArea;
        this.blueMarkerArea.subtract(redMarkerArea);
        this.redMarkerArea = redMarkerArea;
        this.redMarkerArea.subtract(yellowMarkerArea);
        this.yellowMarkerArea = yellowMarkerArea;

        this.colorGroup = new Area();
        this.colorGroup.add(redMarkerArea);
        this.colorGroup.add(blueMarkerArea);
        this.colorGroup.add(yellowMarkerArea);

        this.commandGroup = commandBarArea;

        this.add(commandBarArea.getCommandGroup());
        this.add(commandBarArea.getSizeGroup());
        this.add(this.colorGroup);
    }

    /**
     * @return Returns the biggerStrokeArea.
     */
    public Area getBiggerStrokeArea() {
        return this.commandGroup.getBiggerStrokeArea();
    }

    /**
     * @return Returns the blueMarkerArea.
     */
    public Area getBlueMarkerArea() {
        return blueMarkerArea;
    }

    /**
     * @return Returns the colorGroup.
     */
    public Area getColorGroup() {
        return colorGroup;
    }

    /**
     * @return Returns the commandGroup.
     */
    public Area getCommandGroup() {
        return commandGroup;
    }

    /**
     * @return Returns the redMarkerArea.
     */
    public Area getRedMarkerArea() {
        return redMarkerArea;
    }

    /**
     * @return Returns the redoArea.
     */
    public Area getRedoArea() {
        return this.commandGroup.getRedoArea();
    }

    /**
     * @return Returns the sizeGroup.
     */
    public Area getSizeGroup() {
        return this.commandGroup.getSizeGroup();
    }

    /**
     * @return Returns the smallerStrokeArea.
     */
    public Area getSmallerStrokeArea() {
        return this.commandGroup.getSmallerStrokeArea();
    }

    /**
     * @return Returns the undoArea.
     */
    public Area getUndoArea() {
        return this.commandGroup.getUndoArea();
    }

    /**
     * @return Returns the yellowMarkerArea.
     */
    public Area getYellowMarkerArea() {
        return yellowMarkerArea;
    }
}
